//  R2.java
//
//  Author:
//       Juan J. Durillo <juanjo.durillo@gmail.com>
//
//  Copyright (c) 2013 Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

package etmo.qualityIndicator;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.StringTokenizer;

import etmo.core.SolutionSet;

public class R2 {

	public etmo.qualityIndicator.util.MetricsUtil utils_;
	double[][] matrix_ = null;
	double[][] lambda_ = null;
	int nObj_ = 0;

	/**
	 * Constructor Creates a new instance of the R2 indicator for a problem with
	 * two objectives and 100 lambda vectors
	 */
	public R2() {
		utils_ = new etmo.qualityIndicator.util.MetricsUtil();

		// by default it creates an R2 indicator for a two dimensions probllem
		// and
		// uses only 100 weight vectors for the R2 computation
		nObj_ = 2;
		// generating the weights
		lambda_ = new double[100][2];
		for (int n = 0; n < 100; n++) {
			double a = 1.0 * n / (100 - 1);
			lambda_[n][0] = a;
			lambda_[n][1] = 1 - a;
		} // for

	} // R2

	/**
	 * Constructor Creates a new instance of the R2 indicator for a problem with
	 * two objectives and N lambda vectors
	 */
	public R2(int nVectors) {
		utils_ = new etmo.qualityIndicator.util.MetricsUtil();

		// by default it creates an R2 indicator for a two dimensions probllem
		// and
		// uses only <code>nVectors</code> weight vectors for the R2 computation
		nObj_ = 2;
		// generating the weights
		lambda_ = new double[nVectors][2];
		for (int n = 0; n < nVectors; n++) {
			double a = 1.0 * n / (nVectors - 1);
			lambda_[n][0] = a;
			lambda_[n][1] = 1 - a;
		} // for

	} // R2

	/**
	 * Constructor Creates a new instance of the R2 indicator for nDimensiosn It
	 * loads the weight vectors from the file fileName
	 */
	public R2(int nObj, String file) {
		utils_ = new etmo.qualityIndicator.util.MetricsUtil();
		// A file is indicated, the weights are taken from there

		// by default it creates an R2 indicator for a two dimensions probllem
		// and
		// uses only <code>nVectors</code> weight vectors for the R2 computation
		nObj_ = nObj;
		// generating the weights

		// reading weights
		try {
			// Open the file
			FileInputStream fis = new FileInputStream(file);
			InputStreamReader isr = new InputStreamReader(fis);
			BufferedReader br = new BufferedReader(isr);

			int numberOfObjectives = 0;
			int i = 0;
			int j = 0;
			String aux = br.readLine();
			LinkedList<double[]> list = new LinkedList<double[]>();
			while (aux != null) {
				StringTokenizer st = new StringTokenizer(aux);
				j = 0;
				numberOfObjectives = st.countTokens();
				double[] vector = new double[nObj];
				while (st.hasMoreTokens()) {
					double value = new Double(st.nextToken());
					vector[j++] = value;
				}
				list.add(vector);
				aux = br.readLine();
			}
			br.close();

			// convert the LinkedList into a vector
			lambda_ = new double[list.size()][];
			int index = 0;
			for (double[] aList : list)
				lambda_[index++] = aList;

		} catch (Exception e) {
			System.out.println("initUniformWeight: failed when reading for file: " + file);
			e.printStackTrace();
		}
	} // R2

	/**
	 * Returns the R2 indicator value of a given front
	 * 
	 * @param front
	 *            The front
	 * @param trueParetoFront
	 *            The true Pareto front
	 * @param numberOfObjectives
	 *            The number of objectives
	 * @param lambda
	 *            A vector containing the lambda vectors for R2
	 */
	private double R2Withouth(double[][] approximation, double[][] paretoFront, int index) {

		/**
		 * Stores the maximum values of true Pareto front.
		 */
		double[] maximumValue;

		/**
		 * Stores the minimum values of the true Pareto front.
		 */
		double[] minimumValue;

		/**
		 * Stores the normalized front.
		 */
		double[][] normalizedApproximation;

		/**
		 * Stores the normalized true Pareto front.
		 */
		double[][] normalizedParetoFront;

		// STEP 1. Obtain the maximum and minimum values of the Pareto front
		maximumValue = utils_.getMaximumValues(paretoFront, nObj_);
		minimumValue = utils_.getMinimumValues(paretoFront, nObj_);

		// STEP 2. Get the normalized front and true Pareto fronts
		normalizedApproximation = utils_.getNormalizedFront(approximation, maximumValue, minimumValue);
		normalizedParetoFront = utils_.getNormalizedFront(paretoFront, maximumValue, minimumValue);

		// STEP 3. compute all the matrix of tchebicheff values if it is null
		// if (matrix_ == null) {
		matrix_ = new double[approximation.length][lambda_.length];
		for (int i = 0; i < approximation.length; i++) {
			for (int j = 0; j < lambda_.length; j++) {
				matrix_[i][j] = lambda_[j][0] * Math.abs(normalizedApproximation[i][0]);
				for (int n = 1; n < nObj_; n++) {
					matrix_[i][j] = Math.max(matrix_[i][j], lambda_[j][n] * Math.abs(normalizedApproximation[i][n]));
				}
			}
		}
		// }

		// STEP45. Compute the R2 value withouth the point
		double sumWithout = 0.0;
		for (int i = 0; i < lambda_.length; i++) {

			double tmp;
			if (index != 0) {
				tmp = matrix_[0][i];
			} else {
				tmp = matrix_[1][i];
			}
			for (int j = 0; j < approximation.length; j++) {
				if (j != index) {
					tmp = Math.min(tmp, matrix_[j][i]);
				}
			}
			sumWithout += tmp;
		}

		// STEP 5. Return the R2 value
		return sumWithout / (double) lambda_.length;

	} // R2

	/**
	 * Returns the element contributing the most to the R2 indicator
	 * 
	 * @param front
	 *            The front
	 * @param trueParetoFront
	 *            The true Pareto front
	 * @param numberOfObjectives
	 *            The number of objectives
	 * @param lambda
	 *            A vector containing the lambda vectors for R2
	 */
	public int getBest(double[][] approximation, double[][] paretoFront) {
		int index_best = -1;
		double value = Double.NEGATIVE_INFINITY;

		for (int i = 0; i < approximation.length; i++) {
			double aux = this.R2Withouth(approximation, paretoFront, i);
			if (aux > value) {
				index_best = i;
				value = aux;
			}
		}

		return index_best;
	} // getBest

	/**
	 * Returns the element contributing the less to the R2
	 * 
	 * @param front
	 *            The front
	 * @param trueParetoFront
	 *            The true Pareto front
	 * @param numberOfObjectives
	 *            The number of objectives
	 * @param lambda
	 *            A vector containing the lambda vectors for R2
	 */
	public int getWorst(double[][] approximation, double[][] paretoFront) {
		int index_worst = -1;
		double value = Double.POSITIVE_INFINITY;

		for (int i = 0; i < approximation.length; i++) {
			double aux = this.R2Withouth(approximation, paretoFront, i);
			if (aux < value) {
				index_worst = i;
				value = aux;
			}
		}

		return index_worst;
	} // getWorst

	/**
	 * Returns the element contributing the most to the R2
	 * 
	 * @param front
	 *            A solution set
	 */
	public int getBest(SolutionSet set) {
		double[][] approximationFront = set.writeObjectivesToMatrix();
		double[][] trueFront = set.writeObjectivesToMatrix();

		return this.getBest(approximationFront, trueFront);
	} // getWorst

	/**
	 * Returns the element contributing the less to the R2
	 * 
	 * @param front
	 *            The front
	 * @param trueParetoFront
	 *            The true Pareto front
	 */
	public int getWorst(SolutionSet set) {
		double[][] approximationFront = set.writeObjectivesToMatrix();
		double[][] trueFront = set.writeObjectivesToMatrix();

		return this.getWorst(approximationFront, trueFront);
	} // getWorst

	/**
	 * Returns the element contributing the most to the R2 indicator
	 * 
	 * @param front
	 *            The front
	 * @param trueParetoFront
	 *            The true Pareto front
	 * @param numberOfObjectives
	 *            The number of objectives
	 * @param lambda
	 *            A vector containing the lambda vectors for R2
	 */
	public int[] getNBest(double[][] approximation, double[][] paretoFront, int N) {
		int[] index_bests = new int[approximation.length];
		double[] values = new double[approximation.length];

		for (int i = 0; i < approximation.length; i++) {
			values[i] = this.R2Withouth(approximation, paretoFront, i);
			index_bests[i] = i;
		} // for

		// sorting the values and index_bests
		for (int i = 0; i < approximation.length; i++) {
			for (int j = i; j < approximation.length; j++) {
				if (values[j] < values[i]) {
					double aux = values[j];
					values[j] = values[i];
					values[i] = aux;

					int aux_index = index_bests[j];
					index_bests[j] = index_bests[i];
					index_bests[i] = aux_index;
				}
			}
		}

		int[] res = new int[N];
		for (int i = 0; i < N; i++) {
			res[i] = index_bests[i];
		}

		return res;
	} // getBest

	/**
	 * Returns the indexes of the N best solutions according to this indicator
	 * 
	 * @param SolutionSet
	 *            the solution set for which the best solutions are computed
	 */
	public int[] getNBest(SolutionSet set, int N) {
		double[][] approximationFront = set.writeObjectivesToMatrix();
		double[][] trueFront = set.writeObjectivesToMatrix();

		return this.getNBest(approximationFront, trueFront, N);

	} // getNBest

	/**
	 * Returns the R2 indicator value of a given front
	 * 
	 * @param front
	 *            The front
	 * @param trueParetoFront
	 *            The true Pareto front
	 * @param numberOfObjectives
	 *            The number of objectives
	 * @param lambda
	 *            A vector containing the lambda vectors for R2
	 */
	public double R2(double[][] approximation, double[][] paretoFront) {

		/**
		 * Stores the maximum values of true pareto front.
		 */
		double[] maximumValue;

		/**
		 * Stores the minimum values of the true pareto front.
		 */
		double[] minimumValue;

		/**
		 * Stores the normalized front.
		 */
		double[][] normalizedApproximation;

		/**
		 * Stores the normalized true Pareto front.
		 */
		double[][] normalizedParetoFront;

		// STEP 1. Obtain the maximum and minimum values of the Pareto front
		maximumValue = utils_.getMaximumValues(paretoFront, nObj_);
		minimumValue = utils_.getMinimumValues(paretoFront, nObj_);

		// STEP 2. Get the normalized front and true Pareto fronts
		normalizedApproximation = utils_.getNormalizedFront(approximation, maximumValue, minimumValue);
		normalizedParetoFront = utils_.getNormalizedFront(paretoFront, maximumValue, minimumValue);

		// STEP 3. compute all the matrix of tchebicheff values if it is null
		// if (matrix_ == null) {
		matrix_ = new double[approximation.length][lambda_.length];
		for (int i = 0; i < approximation.length; i++) {
			for (int j = 0; j < lambda_.length; j++) {
				matrix_[i][j] = lambda_[j][0] * Math.abs(normalizedApproximation[i][0]);
				for (int n = 1; n < nObj_; n++) {
					matrix_[i][j] = Math.max(matrix_[i][j], lambda_[j][n] * Math.abs(normalizedApproximation[i][n]));
				}
			}
		}
		// }

		// STEP 4. The matrix is not null. Compute the R2 value
		double sum = 0.0;
		for (int i = 0; i < lambda_.length; i++) {
			double tmp = matrix_[0][i];
			for (int j = 1; j < approximation.length; j++) {
				tmp = Math.min(tmp, matrix_[j][i]);
			}
			sum += tmp;
		}

		// STEP 5. Return the R2 value
		return sum / (double) lambda_.length;

	} // R2

	/**
	 * Returns the R2 indicator of a given population, using as a reference
	 * point 0, 0. Normalization is using taking into account the population
	 * itself
	 * 
	 * @param set
	 * @return
	 */
	public double R2(SolutionSet set) {
		double[][] approximationFront = set.writeObjectivesToMatrix();
		double[][] trueFront = set.writeObjectivesToMatrix();

		return this.R2(approximationFront, trueFront);

	} // R2

	/**
	 * Returns the R2 indicator value of a given front
	 * 
	 * @param front
	 *            The front
	 * @param trueParetoFront
	 *            The true Pareto front
	 * @param numberOfObjectives
	 *            The number of objectives
	 * @param lambda
	 *            A vector containing the lambda vectors for R2
	 */
	public double R2Without(SolutionSet set, int index) {
		double[][] approximationFront = set.writeObjectivesToMatrix();
		double[][] trueFront = set.writeObjectivesToMatrix();
		return this.R2(approximationFront, trueFront);
	} // R2ContributionWithout

	/**
	 * This class can be call from the command line. At least three parameters
	 * are required: 1) the name of the file containing the front, 2) the number
	 * of objectives 2) a file containing the reference point / the Optimal
	 * Pareto front for normalizing 3) the file containing the weight vector
	 */
	public static void main(String args[]) {
		if (args.length < 3) {
			System.err.println("Error using R2. Usage: \n java jmetal.qualityIndicator.Hypervolume "
					+ "<SolutionFrontFile> " + "<TrueFrontFile> " + "<getNumberOfObjectives>");
			System.exit(1);
		}

		// Create a new instance of the metric
		R2 qualityIndicator;
		// Read the front from the files

		int nObj = new Integer(args[2]);

		if (nObj == 2 && args.length == 3) {
			qualityIndicator = new R2();

		} else {
			qualityIndicator = new R2(nObj, args[3]);
		}

		double[][] approximationFront = qualityIndicator.utils_.readFront(args[0]);

		double[][] paretoFront = qualityIndicator.utils_.readFront(args[1]);

		// Obtain delta value
		double value = qualityIndicator.R2(approximationFront, paretoFront);

		System.out.println(value);

		System.out.println(qualityIndicator.R2Withouth(approximationFront, paretoFront, 1));
		System.out.println(qualityIndicator.R2Withouth(approximationFront, paretoFront, 15));
		System.out.println(qualityIndicator.R2Withouth(approximationFront, paretoFront, 25));
		System.out.println(qualityIndicator.R2Withouth(approximationFront, paretoFront, 75));

	} // main
} // R2
